if __debug__: # invoke python with -O or -OO to unset
    from SimPy.SimulationTrace import *
else:
    from SimPy.Simulation import *

from math import floor;

class Person(Process):
    '''A typical person in the network'''

    def __init__(self, pid, sim, universe, timezone=0, tweet_value_mode=0.1):
        '''Initialize the person'''
        self.name = "%d" % pid
        Process.__init__(self, name = self.name, sim = sim)

        self.sim = sim
        self.tweet_history = []     # time-tweet pairs
        self.tweet_inbox = []; # tweet size list
        self.tweet_last_consumed = None
        self.pid = pid;
        self.universe = universe;
        self.follows = []; # IDs of the people you follow
        self.tweet_value_mode = tweet_value_mode # mode of tweet values
        # Tweet value is chosen off a triangular distribution between 0 and 1.
        # Default value of this is 0.1 or 10% by Sturgeon's Law. This decides
        # the probability of tweeting. Every user is awake for 16 hours, which
        # gives a 0.375 probability of tweeting.
        self.tweet_prob = 0.0375 / self.tweet_value_mode
        self.reply_prob = 0.025 / self.tweet_value_mode
        self.cumulative_value = 0.0

        self.timezone = timezone; # initial delay
        # ASSERTION: everyone runs a 16/8 wake/sleep schedule.
        # because we yield before the loop, it means we get proper
        # timezoned scheduling if you turn the timezones on.

        # ASSERTION: avg read speed lands in the 20-22.5 crs range,
        # but we're restricting it to this range for simplicity.
        self.read_speed = random.gauss(21.25, 0.40);
        if self.read_speed > 22.5:
            self.read_speed = 22.5;
        elif self.read_speed < 20:
            self.read_speed = 20;

    def add_tweet(self, tweet):
        '''Adds a tweet to my inbox.'''
        self.tweet_inbox.append(tweet);

    def run(self):
        '''Main process function'''

        # start out by yelding for the number of hours your timezone represents
        # self.sim.report("Person %d is in timezone %d." % (self.pid, self.timezone));
        yield hold, self, 60*60*self.timezone;

        active_hours = 0;

        while True:
            # ----------------------------------------------------------------------
            #
            #  Outline algorithm here.
            #
            # ----------------------------------------------------------------------

            # ASSERTION: whatever is done here must take 5 mins.
            # Avg number of tweets pr month is 174, we round that to 6 tweets a day.
            # SUGGESTION: Make tweet probability randomized.  Power law distribution?
            # MWW: Unsure about what distribution to use.  I expect it to have little
            #      effect until we start adding replies and value to the model.

            # If the tweet values follow Sturgeon's Law (90% of everything is
            # crap), then the probability of tweeting is 0.375. If the person
            # decides to tweet higher quality tweets, then the probability of
            # tweeting will linearly decrease.
            
            # time to spend is 300 seconds (5 minutes)
            read_time = 300;
        
            # You tweet first
            read_time -= self.tweet(time_avail = read_time);
            
            # Then you read as much as you can
            # Read as many as you have time for.
            # Reading speed: 200-225 = 1200-1350 crpmin, 20-22.5 crs

            i = len(self.tweet_inbox);

            # self.sim.report("%s has %d tweets in her inbox." % (self.pid, i));

            while read_time > 0 and i > 0:
                # you spend the amount of time reading the length of the tweet
                # plus a context/comprehension switch of 5 seconds.
                (tweet_size, tweet_value) = self.tweet_inbox[i - 1]
                read_tweet_time = tweet_size / 20 + 5;
                if(read_tweet_time < read_time): # you have time to read the tweet
                    # self.sim.report("%s read a tweet." % self.pid);
                    read_time -= read_tweet_time;
                    self.cumulative_value += tweet_value;
                    self.tweet_inbox.pop();

                    # ASSERTION: reply rate is 25% * your tweet quality stds
                    if random.random() < self.reply_prob:
                        # When replying, degrade tweet value by a factor of .5
                        read_time -= self.tweet(value = tweet_value / 2, time_avail = read_time);
                else:
                    break;
                i = i-1;

            # ASSERTION: people tweet 24 hrs a day, 5 mins every hour

            # REPORT: any left over tweets in the inbox.
            if len(self.tweet_inbox) > 0:
                self.sim.report("%s stopped reading with %d unread tweets." %
                                (self.pid, len(self.tweet_inbox)));

            # ASSERTION: all users are on a 16/8 wake/sleep schedule
            if active_hours == 16:
                active_hours = 0;
                # self.sim.report("User %d has been up for 16 hours, sleeping." % self.pid);
                yield hold, self, 60*60*8;
            else:
                active_hours += 1;
                yield hold, self, 60 * 60;  # Sleep for 60 minutes.

            # NOTE: our 5 minute tweet & read time thing, is just an illusion
            # in the system, we'll have to add some stuff if it is to happen
            # in real simulation time, but we're not going to.

    def tweet_time(self, size):
        # ASSERTION: it takes 30 seconds to produce a tweet of size 75
        # (because 2.5 seconds pr character makes it easy to calculate)
        # SUGGESTION: randomize that time?
        return size/2.5;

    def tweet(self, size=0, value=0, time_avail=-1):
        '''Tweet with given size and value'''
        
        time_taken = 0
        
        # ASSERTION: Tweet size randomized with a normal distribution,
        # mean = 72 (as pr Ted's results), sdev = 20 (Morten's rough guess)
        if size == 0:
            tweet_size = floor(random.gauss(72, 20));
        else:
            tweet_size = size;
            
        time_taken += self.tweet_time(tweet_size);
        # Do nothing if there isn't enough time.
        if time_avail >= 0 and time_taken > time_avail:
            # self.sim.report("%d has no time to tweet." % self.pid);
            return 0;
            
        if value == 0:
            tweet_value = random.triangular(0.0001, self.tweet_value_mode, 1);
        else:
            tweet_value = value;
        
        # tweet size constraint, 140 chars
        if tweet_size > 140:
            tweet_size = 140;
        
        # see comment regarding tweet probability.
        if random.random() < self.tweet_prob:
            # self.sim.report("%d tweeted with length %d." % (self.pid, tweet_size));
            for (pid, conn_val) in self.follows:
                # for each person who follows me...  Add a tweet having the
                # value depending on the value of the tweet and the value of
                # the connection.
                tweet = (tweet_size, tweet_value * conn_val)
                self.universe.persons[pid].add_tweet(tweet);
            # btw: approx avg runtime for 1 day simulation when searching
            # the whole row at 1000 users: 12s
            # with a list of follows: 2.5s
            # turns out it's now doable to run a 10,000 user simulation

        return time_taken;

    
